﻿namespace ReplayDataFile
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.CompilerServices;

    public class BlockDataWriter : BlockDataBase
    {
        private int blockCount;
        private int blockSize;
        private BlockFileWriter curBfWriter;
        private DataTypeInfo[] dataTypes;
        public const int EVENT_DATATYPE = -1;
        private bool isAutoAppendBlock;
        private bool isZipRecord;
        private int maxDataTypes;
        private int maxFileCount;
        private uint maxFileId;
        private int recIndexInterval;

        public event AddNewRecordEvnetHandler AddNewRecord;

        public BlockDataWriter(string filePath, int maxFileCount, int blockCount, int blockSize, int maxDataTypes, ICollection<DataTypeInfo> dataTypes) : base(filePath)
        {
            this.recIndexInterval = 100;
            this.maxFileId = 0;
            this.maxFileCount = maxFileCount;
            this.blockCount = blockCount;
            this.blockSize = blockSize;
            this.maxDataTypes = maxDataTypes;
            if ((dataTypes != null) && (dataTypes.Count > 0))
            {
                this.dataTypes = new DataTypeInfo[dataTypes.Count];
                dataTypes.CopyTo(this.dataTypes, 0);
            }
            this.IsAutoAppendDataType = true;
            this.IsZipRecord = true;
            this.IsWriteEventRecord = true;
            this.DataBreakTimeLen = 10;
        }

        private void bfWriter_Overflow(long firstRecordNo, List<DataRecord> remainRecordList)
        {
            this.curBfWriter = this.CreatetNextFileWriter(firstRecordNo);
            if (this.curBfWriter != null)
            {
                foreach (DataRecord record in remainRecordList)
                {
                    this.curBfWriter.WriteRecord(record);
                }
                this.Flush();
            }
        }

        public override bool Close()
        {
            lock (this)
            {
                this.Flush();
                if (this.curBfWriter != null)
                {
                    this.curBfWriter.Close();
                    this.curBfWriter = null;
                }
                base.IsOpened = false;
                return true;
            }
        }

        private string CreateNewFileName()
        {
            int num = 1;
            while (true)
            {
                string path = string.Format(@"{0}\{1}.hbf", base.dataFilePath, num++);
                if (!File.Exists(path))
                {
                    return path;
                }
            }
        }

        private BlockFileWriter CreatetNextFileWriter(long firstRecordNo)
        {
            BlockFileWriter bfWriter = null;
            uint fileId = this.maxFileId + 1;
            if (this.IsReuseOldDataFile())
            {
                if (base.FileList.Count == 0)
                {
                    return null;
                }
                BlockFile item = base.FileList[0];
                bfWriter = new BlockFileWriter(item.FileName, fileId, this.blockCount, this.blockSize, this.maxDataTypes, this.dataTypes, firstRecordNo);
                base.FileList.Remove(item);
                base.FileList.Add(item);
            }
            else if (base.FreeFileList.Count > 0)
            {
                BlockFile file2 = base.FreeFileList[0];
                base.FreeFileList.Remove(file2);
                bfWriter = new BlockFileWriter(file2.FileName, fileId, this.blockCount, this.blockSize, this.maxDataTypes, this.dataTypes, firstRecordNo);
                base.FileList.Add(file2);
            }
            else
            {
                bfWriter = new BlockFileWriter(this.CreateNewFileName(), fileId, this.blockCount, this.blockSize, this.maxDataTypes, this.dataTypes, firstRecordNo);
                base.FileList.Add(new BlockFile(bfWriter.FileName));
            }
            if (this.InitNewBlockFileWriter(bfWriter, false))
            {
                this.maxFileId++;
            }
            else
            {
                bfWriter = null;
            }
            return bfWriter;
        }

        public void Flush()
        {
            if (this.curBfWriter != null)
            {
                this.curBfWriter.Flush();
            }
        }

        public long GetCurrentRecordNo()
        {
            if (this.curBfWriter == null)
            {
                return 0L;
            }
            return this.curBfWriter.Header.RecordNo;
        }

        public int GetDataType(string dataTypeName)
        {
            if (this.curBfWriter == null)
            {
                return 0;
            }
            return this.curBfWriter.GetDataTypeId(dataTypeName);
        }

        private bool InitNewBlockFileWriter(BlockFileWriter bfWriter, bool isOpenExist)
        {
            if (bfWriter == null)
            {
                return false;
            }
            bfWriter.IsZipRecord = this.IsZipRecord;
            bfWriter.RecordIndexInterval = this.RecordIndexInterval;
            bfWriter.IsAutoAppendBlock = this.isAutoAppendBlock;
            if (!bfWriter.Open(isOpenExist))
            {
                return false;
            }
            if ((this.IsWriteEventRecord && (bfWriter.DataTypeDir.GetDirEntry(-1) == null)) && !bfWriter.AddDataType(-1, true))
            {
                return false;
            }
            bfWriter.Overflow += new FileOverflowEventHandler(this.bfWriter_Overflow);
            this.curBfWriter = bfWriter;
            return true;
        }

        private bool IsReuseOldDataFile()
        {
            if ((this.maxFileCount > 0) && (base.FileList.Count >= this.maxFileCount))
            {
                return true;
            }
            if ((base.FileList.Count > 1) && (this.MaxDataStoreTimeLen.TotalSeconds > 0.0))
            {
                DateTime time;
                DateTime time2;
                List<BlockFile> bfileList = new List<BlockFile>();
                if (base.FileList.Count > 2)
                {
                    bfileList.AddRange(base.FileList.GetRange(1, base.FileList.Count - 2));
                }
                BlockFile item = base.FileList[base.FileList.Count - 1];
                if (item.Open())
                {
                    item.Close();
                    bfileList.Add(item);
                }
                BlockDataBase.GetDataTimeRange(bfileList, out time, out time2);
                TimeSpan span = (TimeSpan) (time2 - time);
                if (span > this.MaxDataStoreTimeLen)
                {
                    return true;
                }
            }
            if ((this.DriveMinFreeSpace > 0) && ((base.dataFilePath != null) && (base.dataFilePath.Length >= 2)))
            {
                try
                {
                    DriveInfo info = new DriveInfo(base.dataFilePath.Substring(0, 2));
                    if (info.AvailableFreeSpace < ((this.DriveMinFreeSpace * 0x400) * 0x400))
                    {
                        return true;
                    }
                }
                catch (Exception exception)
                {
                    Trace.WriteLine(exception.Message);
                    return false;
                }
            }
            return false;
        }

        public override bool Open()
        {
            lock (this)
            {
                if (!Directory.Exists(base.dataFilePath))
                {
                    Directory.CreateDirectory(base.dataFilePath);
                }
                if (!base.Open())
                {
                    return false;
                }
                this.RemoveIncorrectFreeFile();
                if (base.FileList.Count > 0)
                {
                    BlockFile file = base.FileList[base.FileList.Count - 1];
                    this.maxFileId = file.Header.FileId;
                    BlockFileWriter bfWriter = new BlockFileWriter(file.FileName, this.maxFileId, this.blockCount, this.blockSize, this.maxDataTypes, this.dataTypes, file.Header.RecordNo);
                    base.IsOpened = this.InitNewBlockFileWriter(bfWriter, true);
                }
                else
                {
                    base.IsOpened = this.CreatetNextFileWriter(1L) != null;
                }
                return base.IsOpened;
            }
        }

        private void RemoveIncorrectFreeFile()
        {
            List<BlockFile> list = new List<BlockFile>();
            foreach (BlockFile file in base.FreeFileList)
            {
                if (((file.Header.BlockCount != this.blockCount) || (file.Header.BlockSize != this.blockSize)) || (file.DataTypeDir.MaxDataTypes < this.maxDataTypes))
                {
                    list.Add(file);
                }
            }
            foreach (BlockFile file in list)
            {
                File.Delete(file.FileName);
                base.FreeFileList.Remove(file);
            }
        }

        private bool WriteEventRecord(DataRecordType recType, DateTime time, byte[] dataBuffer)
        {
            if (!this.IsWriteEventRecord)
            {
                return false;
            }
            if (this.curBfWriter == null)
            {
                return false;
            }
            byte[] buffer = (dataBuffer == null) ? new byte[0] : dataBuffer;
            DataRecord rec = new DataRecord {
                DataType = -1,
                Time = time,
                DataVersion = 0,
                RecType = recType,
                DataBuffer = buffer
            };
            if (!this.curBfWriter.WriteRecord(rec))
            {
                return false;
            }
            if (this.AddNewRecord != null)
            {
                this.AddNewRecord(rec);
            }
            DataTypeInfo[] dataTypes = this.curBfWriter.DataTypeDir.GetDataTypes();
            foreach (DataTypeInfo info in dataTypes)
            {
                if (info.DataType != -1)
                {
                    rec = new DataRecord {
                        DataType = info.DataType,
                        Time = time,
                        DataVersion = 0,
                        RecType = recType,
                        DataBuffer = buffer
                    };
                    if (!this.curBfWriter.WriteRecord(rec))
                    {
                        return false;
                    }
                    if (this.AddNewRecord != null)
                    {
                        this.AddNewRecord(rec);
                    }
                }
            }
            return true;
        }

        public bool WriteRecord(DataRecord rec)
        {
            if (rec == null)
            {
                return false;
            }
            if (this.curBfWriter == null)
            {
                return false;
            }
            if (rec.RecType == DataRecordType.ChangeDataRecord)
            {
                throw new Exception("当前版本不支持变化记录！");
            }
            if (this.IsAutoAppendDataType && (this.curBfWriter.DataTypeDir.GetDirEntry(rec.DataType) == null))
            {
                this.curBfWriter.AddDataType(rec.DataType, true);
            }
            if (!DataRecord.IsDataRecordType(rec.RecType))
            {
                return this.WriteEventRecord(rec.RecType, rec.Time, rec.DataBuffer);
            }
            if (this.AddNewRecord != null)
            {
                this.AddNewRecord(rec);
            }
            return this.curBfWriter.WriteRecord(rec);
        }

        public BlockFileWriter CurFileWriter
        {
            get
            {
                return this.curBfWriter;
            }
        }

        public int DataBreakTimeLen { get; set; }

        public int DataRecordFormatVersion
        {
            get
            {
                Debug.Assert(this.curBfWriter != null);
                return this.curBfWriter.Header.DataRecordFormatVersion;
            }
            set
            {
                Debug.Assert(this.curBfWriter != null);
                this.curBfWriter.Header.DataRecordFormatVersion = value;
            }
        }

        public int DriveMinFreeSpace { get; set; }

        public int FilePurpose
        {
            get
            {
                Debug.Assert(this.curBfWriter != null);
                return this.curBfWriter.Header.FilePurpose;
            }
            set
            {
                Debug.Assert(this.curBfWriter != null);
                this.curBfWriter.Header.FilePurpose = value;
            }
        }

        public bool IsAutoAppendBlock
        {
            get
            {
                return this.isAutoAppendBlock;
            }
            set
            {
                this.isAutoAppendBlock = value;
                if (this.curBfWriter != null)
                {
                    this.curBfWriter.IsAutoAppendBlock = value;
                }
            }
        }

        public bool IsAutoAppendDataType { get; set; }

        public bool IsWriteEventRecord { get; set; }

        public bool IsZipRecord
        {
            get
            {
                return this.isZipRecord;
            }
            set
            {
                this.isZipRecord = value;
                if (this.curBfWriter != null)
                {
                    this.curBfWriter.IsZipRecord = value;
                }
            }
        }

        public TimeSpan MaxDataStoreTimeLen { get; set; }

        public int RecordIndexInterval
        {
            get
            {
                return this.recIndexInterval;
            }
            set
            {
                this.recIndexInterval = value;
                if (this.curBfWriter != null)
                {
                    this.curBfWriter.RecordIndexInterval = value;
                }
            }
        }
    }
}

